zabbix使用自动发现功能监控服务器各JVM进程状态

前言

        为什么需要做服务器jvm自动发现的监控呢?这个事情主要有两点原因:

  1. zabbix默认监控jvm状态是使用jmx中转进行监控的,监控效率比较低下
  2. zabbix使用jmx监控jvm的时候由于一个主机上的键值不能重复,也就导致了一台主机上只能监控一个jvm实例

        以上两点原因导致zabbix通过jmx监控jvm的实现不是很理想,加上最近老大要求收集服务器上面跑的所有java应用的信息,于是自己琢磨了下,还是自己动手,丰衣足食。利用了周末的时间,通过使用shell脚本+java工具jstat+zabbix实现监控主机上多jvm实例的功能。

一、概念的理解

        首先,既然要监控jvm状态,那就必须要了解jvm里面的信息,楼主通过搜索资料加自动脑补,把网上的资料取其精华,去其糟粕,整理了一下。JVM中的内存分类分为堆内存和非堆内存,堆内存是给实际应用使用的,非堆内存是给jvm容器使用的。我们主要关心的是堆内存这块。在堆内存里面,给内存分为如下几块:

  1. Young代(年轻代)
  2. Old代(老年代)
  3. Perm代(永久代)(关于这一点,在JDK7和JDK8中情况不一样,将在后面进行分析)

        其中,年轻代里面又分成了三块,如下:

  1. Eden代(伊甸园代)
  2. survivor0代(0号幸存区)
  3. survivor1代(1号幸存区)

        至于更详细的关于JVM堆内存的信息,各位可以自行百度或者google,我这里就不赘述了,毕竟我也是个半桶水,自己找了点资料外加脑补到的一些东西,不敢在关公门前耍大刀了。

        当然,还得科普一个东西,那就是GC,所谓的GC就是JVM在运行的时候会有一个垃圾回收机制,这个垃圾回收机制是什么情况呢?就是在程序运行的时候会产生很多已经不使用的空间,但还是被占用了的情况,这样会造成很多不必要的浪费,于是JVM就有一个垃圾回收机制,针对程序中已经不使用的内存资源,会进行回收释放,这个过程就叫做GC。当然,关于GC还有很多内容我这里也没有详述,理由同上条。各位看官只需要知道GC是JVM监控里面的一个很重要的参数就行了。

二、JAVA工具的选用

        java工具有很多,关于jvm监控的工具主要有如下几个:

  • jstat
  • jmap
  • jstack

        其中jmap –heap pid可以抓出挺多的关于某个jvm的运行参数,但是老大提醒我最好不要使用jmap进行jvm监控,具体没有说明原因。于是本着打破砂锅问到底的精神,我又去搜了一把,发现了如下内容:

        jmap最主要的危险操作是下面这三种:

  1. jmap -dump

        这个命令执行,JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,并且执行的过程中为了保证dump的信息是可靠的,所以会暂停应用。

  1. jmap -permstat

        这个命令执行,JVM会去统计perm区的状况,这整个过程也会比较的耗时,并且同样也会暂停应用。

  1. jmap -histo:live

        这个命令执行,JVM会先触发gc,然后再统计信息。

        上面的这三个操作都将对应用的执行产生影响,所以建议如果不是很有必要的话,不要去执行。

        所以,从上面三点来看,jmap命令对jvm状态影响还是比较大的,而且执行jmap –heap的时间也比较长,效率较低,予以排除。

        接下来是jstack,这个命令可以深入到JVM里面对JVM运行问题进行排查,据说还可以统计JVM里面的线程数量。但是这个命令执行效率也比较低,被排除掉了。

        于是剩下的只有一个jstat命令了。下面来详细的讲解该命令的使用了,咳咳,各位快点打起点精神来,这可是重头戏来了。

        首先,列出jstat命令的一些使用案例吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
============================================
1.jstat -gc pid
可以显示gc的信息,查看gc的次数,及时间。
其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
9792.0 10048.0 0.0 5143.2 242048.0 220095.4 323200.0 211509.3 186368.0 114451.6 317 4.850 4 0.971 5.821
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 320.0 11776.0 11604.6 260608.0 149759.6 39344.0 38142.6 4528.0 4303.1 5473 24.010 2 0.128 24.138
2.jstat -gccapacity pid
可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,
如 PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,
PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。
其他的可以根据这个类推, OC是old内纯的占用量。
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
87360.0 262144.0 262144.0 9792.0 10048.0 242048.0 174784.0 786432.0 323200.0 323200.0 131072.0 262144.0 186368.0 186368.0 317 4
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
1536.0 174592.0 13312.0 512.0 512.0 11776.0 260608.0 349696.0 260608.0 260608.0 0.0 1083392.0 39344.0 0.0 1048576.0 4528.0 5474 2
3.jstat -gcutil pid
统计gc信息统计。
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 51.19 83.29 65.44 61.41 317 4.850 4 0.971 5.821
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
68.75 0.00 46.74 57.47 96.95 95.03 5474 24.014 2 0.128 24.143
4.jstat -gcnew pid
年轻代对象的信息。
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
9792.0 10048.0 0.0 5143.2 3 15 9792.0 242048.0 198653.2 317 4.850
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
512.0 512.0 352.0 0.0 15 15 512.0 11776.0 8446.4 5474 24.014
5.jstat -gcnewcapacity pid
年轻代对象的信息及其占用量。
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
87360.0 262144.0 262144.0 87360.0 9792.0 87360.0 10048.0 262016.0 242048.0 317 4
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
1536.0 174592.0 13312.0 57856.0 512.0 57856.0 512.0 173568.0 11776.0 5475 2
6.jstat -gcold pid
old代对象的信息。
PC PU OC OU YGC FGC FGCT GCT
186368.0 114451.6 323200.0 211509.3 317 4 0.971 5.821
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
39344.0 38142.6 4528.0 4303.1 260608.0 149783.6 5475 2 0.128 24.148
7.jstat -gcoldcapacity pid
old代对象的信息及其占用量。
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
174784.0 786432.0 323200.0 323200.0 317 4 0.971 5.821
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
260608.0 349696.0 260608.0 260608.0 5475 2 0.128 24.148
8.jstat -gcpermcapacity pid
perm对象的信息及其占用量。
PGCMN PGCMX PGC PC YGC FGC FGCT GCT
131072.0 262144.0 186368.0 186368.0 317 4 0.971 5.821
没有
9.jstat -class pid
显示加载class的数量,及所占空间等信息。
Loaded Bytes Unloaded Bytes Time
25315 45671.7 5976 7754.1 15.19
Loaded Bytes Unloaded Bytes Time
6472 11893.0 0 0.0 5.97
10.jstat -compiler pid
显示VM实时编译的数量等信息。
Compiled Failed Invalid Time FailedType FailedMethod
4219 3 0 63.36 1 org/aspectj/weaver/ResolvedType addAndRecurse
Compiled Failed Invalid Time FailedType FailedMethod
11364 1 0 107.53 1 sun/nio/cs/UTF_8$Decoder decode
11.stat -printcompilation pid
当前VM执行的信息。
Compiled Size Type Method
4219 2232 1 net/spy/memcached/protocol/ascii/BaseGetOpImpl initialize
Compiled Size Type Method
11364 212 1 com/alibaba/rocketmq/client/impl/consumer/RebalanceService run
==================================================

        可以看出上面我列出的命令执行结果为什么有两行呢,这是因为是用不同的jdk版本执行的。

        上面是JDK7执行结果,下面是JDK8执行结果,这两个版本之间输出的结果是有差距的,下面,就来分析为什么会产生这种差异。

JDK7和JDK8中JVM堆内存划分差异

        如果记性好的童鞋们应该还能记得我上面在介绍JVM堆内存分类的时候括号里写的那个东东吧,没错,就是这个东西导致的。在JDK7中的Perm代(永久代)在JDK8中被废除了,取而代之的是Metadata代(元数据代),据说这个元数据代相对于永久代进行了优化,如果不设置最大值的话,默认会按需增长, 不会造成像Perm代中内存占满后会爆出内存溢出的错误,元数据代也可以设置最大值,这样的话,当内存区域被消耗完的时候将会和Perm代一样爆出内存溢出的错误。(PS:原谅我的班门弄斧,只能解释到这一个层面了。)

        好了,解释清楚了JDK7和JDK8的差异以后,接下来我们来解释jstat抓到的这些参数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
jstat命令获取参数解析
======================================================================================
* S0C 年轻代中第一个survivor(幸存区)的容量 (字节)jstat -gcnew $pid|tail -1|awk '{print $1*1024}'
* S0U 年轻代中第一个survivor(幸存区)目前已使用空间 (字节)jstat -gcnew $pid|tail -1|awk '{print $3*1024}'
* S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk '{print $1}'
* S0CMX 年轻代中第一个survivor(幸存区)的最大容量 (字节)jstat -gcnewcapacity $pid|tail -1|awk '{print $4*1024}'
*
* S1C 年轻代中第二个survivor(幸存区)的容量 (字节)jstat -gcnew $pid|tail -1|awk '{print $2*1024}'
* S1U 年轻代中第二个survivor(幸存区)目前已使用空间 (字节)jstat -gcnew $pid|tail -1|awk '{print $4*1024}'
* S1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk '{print $2}'
* S1CMX 年轻代中第二个survivor(幸存区)的最大容量 (字节)jstat -gcnewcapacity $pid|tail -1|awk '{print $6*1024}'
* DSS 当前需要survivor(幸存区)的容量 (字节)(Eden区已满)jstat -gcnew $pid|tail -1|awk '{print $7*1024}'
*
* EC 年轻代中Eden(伊甸园)的容量 (字节)jstat -gcnew $pid|tail -1|awk '{print $8*1024}'
* EU 年轻代中Eden(伊甸园)目前已使用空间 (字节)jstat -gcnew $pid|tail -1|awk '{print $9*1024}'
* ECMX 年轻代中Eden(伊甸园)的最大容量 (字节)jstat -gcnewcapacity $pid|tail -1|awk '{print $8*1024}'
* E 年轻代中Eden(伊甸园)已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk '{print $3}'
*
* NGCMN 年轻代(young)中初始化(最小)的大小 (字节)jstat -gccapacity $pid|tail -1|awk '{print $1*1024}'
* NGCMX 年轻代(young)的最大容量 (字节)jstat -gccapacity $pid|tail -1|awk '{print $2*1024}'
* NGC 年轻代(young)中当前的容量 (字节)jstat -gccapacity $pid|tail -1|awk '{print $3*1024}'
*
* OC Old代的容量 (字节)jstat -gcold $pid|tail -1|awk '{print $3*1024}'
* OU Old代目前已使用空间 (字节)jstat -gcold $pid|tail -1|awk '{print $4*1024}'
* OGCMX old代的最大容量 (字节)jstat -gccapacity $pid|tail -1|awk '{print $8*1024}'
* OGCMN old代中初始化(最小)的大小 (字节)jstat -gccapacity $pid|tail -1|awk '{print $7*1024}'
* O old代已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk '{print $4}'
* OGC old代当前新生成的容量 (字节)jstat -gccapacity $pid|tail -1|awk '{print $9*1024}'
*
* PC Perm(持久代)的容量 (字节)jstat -gccapacity $pid|tail -1|awk '{print $14*1024}'
* PU Perm(持久代)目前已使用空间 (字节)jstat -gc $pid|tail -1|awk '{print $10*1024}'
* PGCMX perm代的最大容量 (字节)jstat -gccapacity $pid|tail -1|awk '{print $12*1024}'
* PGCMN perm代中初始化(最小)的大小 (字节)jstat -gccapacity $pid|tail -1|awk '{print $11*1024}'
* P perm代已使用的占当前容量百分比 jstat -gcutil $pid|tail -1|awk '{print $5*1024}'
* PGC perm代当前新生成的容量 (字节)jstat -gccapacity $pid|tail -1|awk '{print $13*1024}'
*
* YGC 从应用程序启动到采样时年轻代中gc次数jstat -gccapacity $pid|tail -1|awk '{print $15}'
* YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)jstat -gcutil $pid|tail -1|awk '{print $7}'
* FGC从应用程序启动到采样时old代(全gc)gc次数jstat -gccapacity $pid|tail -1|awk '{print $16}'
* FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)jstat -gcutil $pid|tail -1|awk '{print $9}'
* GCT 从应用程序启动到采样时gc用的总时间(s)jstat -gcutil $pid|tail -1|awk '{print $10}'
*
* TT 持有次数限制jstat -gcnew $pid|tail -1|awk '{print $5}'
* MTT 最大持有次数限制jstat -gcnew $pid|tail -1|awk '{print $6}'
*
* Loadedjvm加载class数量
* Unloadedjvm未加载class数量
*
* M元数据区使用比例
* MC当前元数据空间大小
* MU元数据空间使用大小
* MCMN最小元数据容量
* MCMX最大元数据容量
*
* CCS压缩使用比例
* CCSC当前压缩类空间大小
* CCSU压缩类空间使用大小
* CCSMN最小压缩类空间大小
* CCSMX最大压缩类空间大小
====================================================

        好了,上面就是我找到的一些对jstat获取的数据意思的统计,各位看官可以做个参考。
好了,这一章的内容到此基本结束,前面的东西都是一些理论类的东西,没有实际的操作。俗话说,光说不练假把式。接下来,我们将开启下一章的旅程,脚本+jstat的使用。

三、脚本+jstat获取数据

首先,我们来看一下该章节介绍的几个脚本吧:

  1. jvm_list.sh 获取该机器上所有运行的JVM的进程对应的程序根目录以及程序名称

  2. get_jvmlist.sh 将获取的该机器上的所有进程对应的程序名称序列化成json格式并发送给zabbix服务器

  3. get_jvmstatus.sh 通过获取的程序根目录获取到对应的程序进程,再通过jstat抓取数据写入到文件中缓存

  4. set_jvmstatus.sh zabbix通过调用该脚本获取缓存文件中的关于某个JVM进程的状态信息

简单介绍了上面几个脚本的功能,下面我们列出这几个脚本的实际内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat jvm_list.sh
#!/bin/bash
packagePath=/usr/local/etc/scripts/package_path.txt
echo -n >$packagePath
for i in `ps -fC java|tail -n +2|grep -v 'flume'|awk '{print $2}'`;
do
pgrootpath=`ls -l /proc/$i/cwd|awk '{print $NF}'`
if [[ -r $pgrootpath/appconfig ]] && [ `grep ^packagename= $pgrootpath/appconfig|wc -l`==1 ];then
packagename=$(grep ^packagename= $pgrootpath/appconfig 2>/dev/null|awk -F'"' '{print $2}')
elif [[ -r $pgrootpath/webconfig ]] && [ `grep ^packagename= $pgrootpath/webconfig|wc -l`==1 ];then
packagename=$(grep ^packagename= $pgrootpath/webconfig 2>/dev/null|awk -F'"' '{print $2}')
else
packagename=$(basename $pgrootpath)-1.0.0-bin.tar.gz
fi
echo "$packagename $pgrootpath" >> $packagePath
done

        该脚本的目的是先通过使用ps -fC java命令获取该机器上面除了flume进程外的所有其他java进程(我这边使用的是flume来收集业务日志的。)

        然后,通过获取到的PID使用ll /proc/pid/cwd命令获取该进程的程序根目录,后面那些判断是获取该进程对应的包名(这一步各位可以根据自己公司的情况自行修改,我这边取包名的方式并不能够匹配各位公司的设置,在下爱莫能助了。)

        最后是将获取到的程序根目录和包名存放在变量packagePath对应的文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat get_jvmlist.sh
#!/bin/bash
TABLESPACE=`awk '{print $1}' /usr/local/etc/scripts/package_path.txt`
COUNT=`echo "$TABLESPACE" |wc -l`
INDEX=0
echo '{"data":['
echo "$TABLESPACE" | while read LINE; do
echo -n '{"{#TABLENAME}":"'$LINE'"}'
INDEX=`expr $INDEX + 1`
if [ $INDEX -lt $COUNT ]; then
echo ','
fi
done
echo ']}'

        这个脚本的作用就是通过读取文件里面的包名,然后将包名进行json序列化输出,没什么好讲的,套路套一个循环脚本就行。
接下来就是重要的脚本了,调用jstat获取JVM状态,并缓存到文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
cat get_jvmstatus.sh
#!/bin/bash
MAINCLASS="*Main.class"
scriptPath=/usr/local/etc/scripts
cat $scriptPath/package_path.txt|while read line
do
packageName=$(echo $line|awk '{print $1}')
pgRootPath=$(echo $line|awk '{print $2}')
if [[ -d $pgRootPath/tomcat ]];then
pid=$(cat $pgRootPath/tomcat/tomcat.pid)
else
mainPath=$(find $pgRootPath -name $MAINCLASS)
appName=$(echo ${mainPath##*classes/}|sed 's#/#.#g'|sed 's#.class##g')
pid=$(ps -fC java|grep "$appName"|awk '{print $2}')
fi
javaHome=/usr/local/java/jdk1.8.0
#javaHome=/usr/local/java/latest
#if [[ -r $pgRootPath/appconfig ]] && [ `grep ^JAVA_HOME= $pgRootPath/appconfig|wc -l` == 1 ] && [ `grep ^JAVA_HOME= $pgRootPath/appconfig|grep 8|wc -l` == 1 ];then
#javaHome=$(grep ^JAVA_HOME= $pgRootPath/appconfig 2>/dev/null|awk -F'=' '{print $2}')
#javaHome=/usr/local/java/jdk1.8.0
#else
# if [[ -r $pgRootPath/webconfig ]] && [ `grep ^'export JAVA_HOME=' $pgRootPath/webconfig|wc -l` == 1 ] && [ `grep ^'export JAVA_HOME=' $pgRootPath/webconfig|grep 8|wc -l` == 1 ];then
# #javaHome=$(grep ^'export JAVA_HOME=' $pgRootPath/webconfig 2>/dev/null|awk -F'"' '{print $2}')
# javaHome=/usr/local/java/jdk1.8.0
#fi
#fi
#echo --------------------------------$pgRootPath
#echo $javaHome
echo -------------------------------$pid
sleep 5
#echo -n >$scriptPath/package/$packageName
#$javaHome/bin/jstat -gccapacity $pid > ./package/$packageName 2>/dev/null
#$javaHome/bin/jmap -heap $pid>>./package/$packageName 2>/dev/null
echo gcnew >> $scriptPath/package/$packageName 2>/dev/null
$javaHome/bin/jstat -gcnew $pid >> $scriptPath/package/$packageName 2>/dev/null
echo gcutil >> $scriptPath/package/$packageName 2>/dev/null
$javaHome/bin/jstat -gcutil $pid >> $scriptPath/package/$packageName 2>/dev/null
echo gcnewcapacity >> $scriptPath/package/$packageName 2>/dev/null
$javaHome/bin/jstat -gcnewcapacity $pid >> $scriptPath/package/$packageName 2>/dev/null
echo gccapacity >> $scriptPath/package/$packageName 2>/dev/null
$javaHome/bin/jstat -gccapacity $pid >> $scriptPath/package/$packageName 2>/dev/null
#echo gcold >> $scriptPath/package/$packageName 2>/dev/null
#$javaHome/bin/jstat -gcold $pid >> $scriptPath/package/$packageName 2>/dev/null
echo gc >> $scriptPath/package/$packageName 2>/dev/null
$javaHome/bin/jstat -gc $pid >> $scriptPath/package/$packageName 2>/dev/null
echo class >> $scriptPath/package/$packageName 2>/dev/null
$javaHome/bin/jstat -class $pid >> $scriptPath/package/$packageName 2>/dev/null
echo cpu >> $scriptPath/package/$packageName 2>/dev/null
echo -e "CPU\n$( ps aux|grep $pid|grep -v grep|awk '{print $3}')" >> $scriptPath/package/$packageName 2>/dev/null
echo mem >> $scriptPath/package/$packageName 2>/dev/null
echo -e "MEM\n$( ps aux|grep $pid|grep -v grep|awk '{print $6}')" >> $scriptPath/package/$packageName 2>/dev/null
done

        这里面首先是通过获取到程序的根目录,然后我这的java程序除了tomcat跑的之外,其他的java程序都是通过Main.class启动的,所以可以获取到AppName,这样通过ps命令就能找到其对应的PID了,而如果是tomcat启动的进程的话,在程序根目录下面的tomcat目录下有一个tomcat.pid文件里面有该程序的PID。后面被注释的那一端代码其实之前是加上去的,那段代码的作用是判断该进程使用的是JDK7还是JDK8启动的,当初的计划是想着如果是JDK7启动的进程就用JDK7的jstat去获取数据,如果是JDK8启动的进程就用JDK8的jstat去获取数据,后来发现不同版本的JDK获取的数据格式不同,于是。。。。。。后悔莫及的把那段代码注释掉了。后面综合公司实际情况考虑,JDK8的程序用得比较多,JDK7的程序相对来说比较少,并且慢慢都会向JDK8进行转换,所以,权衡利弊之下,之后将jstat的JDK全部换成了JDK8,这样的影响就是获取不到JDK7的永久代数据。当然,各位有兴趣的话,也可以JDK7和JDK8同时使用,在过滤输出文件的时候加一个标志位进行判断,当然,我这里暂时没有做这方面的修改。。。毕竟时间有限。。。

        第四个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
cat set_jvmstatus.sh
#!/bin/bash
packageName=$1
key=$2
if [ $2 == "S0C" -o $2 == "S0U" -o $2 == "S1C" -o $2 == "S1U" -o $2 == "DSS" -o $2 == "EC" -o $2 == "EU" ];then
part=gcnew
elif [ $2 == "S0" -o $2 == "S1" -o $2 == "E" -o $2 == "O" -o $2 == "M" -o $2 == "CCS" -o $2 == "YGCT" -o $2 == "FGCT" -o $2 == "GCT" ];then
part=gcutil
elif [ $2 == "S0CMX" -o $2 == "S1CMX" -o $2 == "ECMX" ];then
part=gcnewcapacity
elif [ $2 == "NGCMN" -o $2 == "NGCMX" -o $2 == "NGC" -o $2 == "OGCMX" -o $2 == "OGCMN" -o $2 == "OGC" -o $2 == "MCMN" -o $2 == "MCMX" -o $2 == "MC" -o $2 == "CCSMN" -o $2 == "CCSMX" -o $2 == "CCSC" -o $2 == "YGC" -o $2 == "FGC" ];then
part=gccapacity
elif [ $2 == "MU" -o $2 == "CCSU" -o $2 == "OC" -o $2 == "OU" ];then
part=gc
elif [ $2 == "Loaded" -o $2 == "Unloaded" ];then
part=class
elif [ $2 == "CPU" ];then
part=cpu
elif [ $2 == "MEM" ];then
part=mem
else
echo "Error input:"
exit 0
fi
case $2 in
S0C)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $1*1024}'
;;
S0U)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $3*1024}'
;;
S0)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $0}'
;;
S0CMX)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $4*1024}'
;;
S1C)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $2*1024}'
;;
S1U)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $4*1024}'
;;
S1)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $2}'
;;
S1CMX)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $6*1024}'
;;
DSS)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $7*1024}'
;;
EC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $8*1024}'
;;
EU)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $9*1024}'
;;
ECMX)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $8*1024}'
;;
E)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $3}'
;;
NGCMN)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $1*1024}'
;;
NGCMX)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $2*1024}'
;;
NGC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $3*1024}'
;;
OC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $7*1024}'
;;
OU)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $8*1024}'
;;
OGCMX)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $8*1024}'
;;
OGCMN)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $7*1024}'
;;
O)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $4}'
;;
OGC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $9*1024}'
;;
M)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $5}'
;;
MC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $13*1024}'
;;
MU)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $10*1024}'
;;
MCMN)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $11*1024}'
;;
MCMX)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $12*1024}'
;;
CCS)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $6}'
;;
CCSC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $13*1024}'
;;
CCSU)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $12*1024}'
;;
CCSMN)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $14*1024}'
;;
CCSMX)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $15*1024}'
;;
YGC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $17}'
;;
YGCT)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $8}'
;;
FGC)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $18}'
;;
FGCT)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $10}'
;;
GCT)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $11}'
;;
TT)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $5}'
;;
MTT)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $6}'
;;
Loaded)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $1}'
;;
Unloaded)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $3}'
;;
CPU)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%f\n", $1}'
;;
MEM)
grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk '{printf "%d\n", $1*1024}'
;;
*)
echo "Error input:"
;;
esac
exit 0

        这套脚本没什么讲的,就是重复的进行一些判断,抓数据并输出(注意,之前写的获取的jstat参数的值其实是不准确的,获取的值是以KB为单位而不是以字节为单位,所以我取完数据后对数据进行成字节为单位了。)

        接下来,讲一下这几个脚本该怎么部署。我这里的zabbix_agentd是通过yum安装的,所以安装在/usr/local目录下,配置文件在/usr/local/etc目录下,需要在zabbix_agentd.conf里面添加下面两行获取数据的key(注意,添加好后一定要记得重启zabbix_agentd进程):

1
2
UserParameter=jmx.discovery,/usr/local/etc/scripts/get_jvmlist.sh
UserParameter=jmx.resource[*],/usr/local/etc/scripts/set_jvmstatus.sh $1 $2

        然后脚本都放置在/usr/local/etc/scripts/目录下,该目录下的脚本权限如下:

1
2
3
4
5
6
-rwxr-xr-x 1 zabbix zabbix 326 3月 26 22:29 get_jvmlist.sh
-rwxr-xr-x 1 root root 2956 3月 28 20:57 get_jvmstatus.sh
-rwxr-xr-x 1 root root 818 3月 26 22:33 jvm_list.sh
drwxr-xr-x 2 zabbix zabbix 4096 3月 26 23:05 package
-rw-r--r-- 1 zabbix zabbix 1947 3月 29 11:23 package_path.txt
-rwxr-xr-x 1 zabbix zabbix 5240 3月 28 20:50 set_jvmstatus.sh

        然后需要在crontab里面定义jvm_list.sh和get_jvmstatus.sh脚本的定时任务,我这里定义的如下:

1
2
* */1 * * * /usr/local/etc/scripts/jvm_list.sh
*/5 * * * * /usr/local/etc/scripts/get_jvmstatus.sh

        注意这两个脚本必须要以root权限去执行,因为里面涉及到的一些命令只有root用户才有权限去执行。

        之后可以手动执行脚本去获取数据,看是否能够抓取到相应的数据。

四、zabbix获取数据

        通过之前的脚本部署,可以在zabbix_server上面通过zabbix_get命令去检查是否获取到了相应的数据:

1
2
zabbix_get -s xx.xx.xx.xx -k jmx.resource[Abcdefg-1.0.0-rc-bin.tar.gz,MEM]
641036288

        这里可以获取到数据了(注意IP被注释掉了,为了保护隐私哈,包名也被刻意修改了)

        接下来就可以部署模板了,至于模板已经做好了,可以直接在附件里面下载。至于模板我制作了一些简单的key的值收集,以及图像的展示,至于监控报警值的设置,由于各个公司的环境不一样,需要各位自己根据自己需求自行设置。